package com.learn.springboot.controller.excel;

import java.io.IOException;
import java.math.BigDecimal;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicInteger;

import javax.servlet.http.HttpServletResponse;

import org.springframework.stereotype.Controller;

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.learn.springboot.config.AppConstants;
import com.learn.springboot.dao.mode.ExportInfoVo;
import com.learn.springboot.dao.request.PageQueryRequest;
import com.learn.springboot.dao.response.ExportDataWrapper;
import com.learn.springboot.dao.response.ExportErrorMsg;
import com.learn.springboot.service.utils.ThreadPoolTaskUtil;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.util.PageUtil;
import cn.hutool.core.util.StrUtil;
import lombok.Builder;
import lombok.extern.slf4j.Slf4j;

/**
 * excel导入导出
 *
 * @author suzheng
 * @date 2020/7/15
 */
@Slf4j
@Controller
public abstract class AbstractExportBaseController {

    /**
     * 导出数据返回
     */
    protected void returnExportResponse(HttpServletResponse response, PageQueryRequest pageQueryRequest,
                                        ExportInfoVo exportInfoVo, ExportExcelService exportExcelService) throws IOException {
        try {
            exportInfoVo.setDataList(wrapperExportDataList(pageQueryRequest, exportExcelService));
            returnExportResponse(response, exportInfoVo);
        } catch (Exception e) {
            String errMsg = ExceptionUtil.getRootCauseMessage(e);
            log.error("导出异常：【{}】", errMsg);
            // 重置response
            response.reset();
            response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
            response.setCharacterEncoding(StandardCharsets.UTF_8.name());
            response.setHeader("Content-disposition", "attachment;filename=导出失败.xlsx");
            ExportErrorMsg exportErrorMsg = ExportErrorMsg.builder().errorMsg(errMsg).build();
            EasyExcel.write(response.getOutputStream(), exportErrorMsg.getClass()).sheet("错误信息")
                    .doWrite(Collections.singletonList(exportErrorMsg));
        }
    }

    /**
     * 导出数据返回
     *
     * @param response
     * @return
     */
    public void returnExportResponse(HttpServletResponse response, ExportInfoVo exportInfoVo) throws IOException {
        try {
            resetResponse(response, exportInfoVo.getOutFileName());
            // 这里需要设置不关闭流
            EasyExcel.write(response.getOutputStream(), exportInfoVo.getDataClass()).autoCloseStream(false)
                    .sheet(StrUtil.trimToNull(exportInfoVo.getSheetName())).doWrite(exportInfoVo.getDataList());
        } catch (Exception e) {
            String errMsg = ExceptionUtil.getRootCauseMessage(e);
            log.error("导出异常：【{}】", errMsg);
            resetResponse(response, "错误信息");
            ExportErrorMsg exportErrorMsg = ExportErrorMsg.builder().errorMsg(errMsg).build();
            EasyExcel.write(response.getOutputStream(), exportErrorMsg.getClass()).sheet("错误信息")
                    .doWrite(Collections.singletonList(exportErrorMsg));
        }
    }

    public void returnMultiSheetResponse(HttpServletResponse response, List<ExportInfoVo> exportInfoVos) throws IOException {
        try {
            resetResponse(response, exportInfoVos.get(0).getOutFileName());

            try (ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream()).needHead(false).autoCloseStream(false).build()) {
                AtomicInteger atomicInteger = new AtomicInteger(0);
                exportInfoVos.forEach(exportInfoVo -> {
                    WriteSheet writeSheet = EasyExcel.writerSheet(atomicInteger.incrementAndGet(), exportInfoVo.getSheetName()).build();
                    excelWriter.write(exportInfoVo.getDataList(), writeSheet);
                });
            }


        } catch (Exception e) {
            String errMsg = ExceptionUtil.getRootCauseMessage(e);
            log.error("导出异常：【{}】", errMsg);
            resetResponse(response, "错误信息");
            ExportErrorMsg exportErrorMsg = ExportErrorMsg.builder().errorMsg(errMsg).build();
            EasyExcel.write(response.getOutputStream(), exportErrorMsg.getClass()).sheet("错误信息")
                    .doWrite(Collections.singletonList(exportErrorMsg));
        }
    }

    /**
     * 导入excel异常回写错误excel
     *
     * @param response
     * @param errMsg
     * @throws IOException
     */
    protected void returnErrExportResponse(HttpServletResponse response, String errMsg) throws IOException {
        log.error("导出异常：【{}】", errMsg);
        resetResponse(response, "错误信息");
        ExportErrorMsg exportErrorMsg = ExportErrorMsg.builder().errorMsg(errMsg).build();
        EasyExcel.write(response.getOutputStream(), exportErrorMsg.getClass()).sheet("错误信息")
                .doWrite(Collections.singletonList(exportErrorMsg));
    }

    private void resetResponse(HttpServletResponse response, String fileName) {
        response.reset();
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        response.setCharacterEncoding(StandardCharsets.UTF_8.name());
        String encodeFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8);
        response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + encodeFileName + ".xlsx");
    }

    private List wrapperExportDataList(PageQueryRequest pageQueryRequest, ExportExcelService exportExcelService)
            throws Exception {
        int totalCount = exportExcelService.getTotalCount(pageQueryRequest);
        log.info("查询导出数据总条数：【{}】参数：【{}】", totalCount, pageQueryRequest);
        if (totalCount < 1) {
            return Collections.emptyList();
        }
        if (totalCount > AppConstants.MAX_EXPORT_SIZE) {
            throw new Exception("导出数据超过" + AppConstants.MAX_EXPORT_SIZE + "条");
        }
        if (totalCount > AppConstants.MAX_EXPORT_SIZE_PER_PAGE) {
            return queryByMultiThread(pageQueryRequest, exportExcelService, totalCount);
        } else {
            return exportExcelService.queryData(pageQueryRequest, AppConstants.MAX_EXPORT_SIZE_PER_PAGE);
        }
    }

    private List queryByMultiThread(PageQueryRequest pageQueryRequest, ExportExcelService exportExcelService,
                                    int totalCount) {
        // 总数量超过配置的每页显示数量，则需要分页查询
        long page = PageUtil.totalPage(totalCount, AppConstants.MAX_EXPORT_SIZE_PER_PAGE);
        int maxTaskSize = Double.valueOf(Math.log(page) * 2.25).intValue();
        int splitSize = new BigDecimal(page + "").divide(new BigDecimal(maxTaskSize), 0, BigDecimal.ROUND_UP).intValue();
        int taskSize = PageUtil.totalPage((int) page, splitSize);
        log.info("多线程导出excel-总页数：【{}】, 页大小：【{}】,任务数：【{}】，splitSize：【{}】", page, AppConstants.MAX_EXPORT_SIZE_PER_PAGE, taskSize, splitSize);

        List<Future<ExportDataWrapper>> futureList = new ArrayList<>((int) page);
        int currentPage = 1;
        for (int taskIndex = 1; taskIndex <= taskSize; taskIndex++) {
            pageQueryRequest.setCurrentPage(currentPage);
            currentPage = currentPage + splitSize;
            try {
                ExportExcelTask exportExcelTask = ExportExcelTask.builder().exportExcelService(exportExcelService)
                        .taskIndex(taskIndex).splitSize(splitSize).pageQueryRequest(BeanUtil.toBean(pageQueryRequest, PageQueryRequest.class)).build();
                Future<ExportDataWrapper> future = ThreadPoolTaskUtil.submit(exportExcelTask);
                futureList.add(future);
            } catch (Exception e) {
                log.error(" System Exception msg[{}].\nSome Exception Occur:[{}]",
                        e.getMessage(), ExceptionUtil.getMessage(e));
            }
        }
        List<ExportDataWrapper> exportDataWrappers = new ArrayList<>(taskSize);
        for (Future<ExportDataWrapper> future : futureList) {
            // 检索并移除表示下一个已完成任务的 Future，如果目前不存在这样的任务，则等待。
            try {
                exportDataWrappers.add(future.get());
            } catch (InterruptedException | ExecutionException e) {
                log.error("System Exception msg[{}].\nSome Exception Occur:[{}]",
                        e.getMessage(), ExceptionUtil.getMessage(e));
            }
        }
        exportDataWrappers.sort(Comparator.comparingInt(ExportDataWrapper::getIndex));
        List dataList = new ArrayList(totalCount);
        exportDataWrappers.forEach(exportDataWrapper -> {
            if (!exportDataWrapper.getList().isEmpty()) {
                dataList.addAll(exportDataWrapper.getList());
            }
        });
        return dataList;
    }


    @Builder
    @Slf4j
    public static class ExportExcelTask<E> implements Callable<ExportDataWrapper<E>> {
        private PageQueryRequest pageQueryRequest;
        private ExportExcelService exportExcelService;
        private int taskIndex;
        private int splitSize = 1;

        @Override
        public ExportDataWrapper call() {
            List dataList = new ArrayList(AppConstants.MAX_EXPORT_SIZE_PER_PAGE);
            int currentPage = pageQueryRequest.getCurrentPage();
            for (int index = 0; index < splitSize; index++) {
                log.info("ExportExcelTask currentPage:【{}】 splitSize：【{}】", currentPage, splitSize);
                pageQueryRequest.setCurrentPage(currentPage);
                currentPage = currentPage + 1;
                List list = exportExcelService.queryData(pageQueryRequest, AppConstants.MAX_EXPORT_SIZE_PER_PAGE);
                if (CollectionUtil.isEmpty(list)) {
                    break;
                }
                dataList.addAll(list);
                if (list.size() < AppConstants.MAX_EXPORT_SIZE_PER_PAGE) {
                    break;
                }
            }
            return ExportDataWrapper.builder().index(taskIndex).list(dataList).build();
        }

    }
}
